/*
 * Copyright 2013 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * Modified by falling on 15-5-1.
 */

package io.github.codefalling.recyclerviewswipedismiss;

import ohos.agp.components.Component;
import ohos.agp.components.element.ShapeElement;
import ohos.multimodalinput.event.TouchEvent;

/**
 * 自定义onTouch监听
 *
 * @since 2021-04-25
 */
public class SwipeDismissRecyclerViewTouchListener implements Component.TouchEventListener {
    private static final int MAX_CLICK_DURATION = 500;

    private static final int HALF = 2;
    private static final int COUNT = 2;
    private static final float ALPHA_RATIO = 1.2f;
    /**
     * 徘徊距离
     */
    private static final float TOUCH_SLOP = 8;
    private static final float MIN_DIS = 1.0f;
    private static final float MVIEWWIDTH = 1000;

    private static final int UNITS = 1000;
    private final int mPosition;
    private final DismissCallbacks mCallbacks;
    private final boolean mIsVertical;
    private final OnItemTouchCallBack mItemTouchCallback;
    private final OnItemClickCallBack mItemClickCallback;
    private long pressStartTime;
    private float pressedX;
    private float pressedY;
    private float mDownX;
    private float mDownY;
    private boolean isSwiping;
    private boolean isPaused;
    private final ShapeElement mBackgroundPressId;
    private final ShapeElement mBackgroundNormalId;
    private boolean isMoveAfterDown;

    /**
     * SwiperTouchListener
     *
     * @param builder
     */
    public SwipeDismissRecyclerViewTouchListener(Builder builder) {
        mPosition = builder.mPosition;
        mCallbacks = builder.mCallbacks;
        mIsVertical = builder.mIsVertical;
        mItemTouchCallback = builder.mItemTouchCallback;
        mItemClickCallback = builder.mItemClickCallback;
        mBackgroundNormalId = builder.mBackgroundNormalId;
        mBackgroundPressId = builder.mBackgroundPressId;
    }

    /**
     * onTouchEvent
     *
     * @param component
     * @param touchEvent
     * @return false
     */
    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        switch (touchEvent.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                downEvent(component, touchEvent);
                return true;
            case TouchEvent.POINT_MOVE:
                isMoveAfterDown = true;
                moveEvent(component, touchEvent);
                return true;
            case TouchEvent.PRIMARY_POINT_UP:
                long pressDuration = System.currentTimeMillis() - pressStartTime;
                float clickX = touchEvent.getPointerPosition(touchEvent.getIndex()).getX();
                float clickY = touchEvent.getPointerPosition(touchEvent.getIndex()).getY();
                float dis = distance(pressedX, pressedY, clickX, clickY);
                Hlog.sop("duration:" + pressDuration + "-----------dis:" + dis);
                if (pressDuration < MAX_CLICK_DURATION && distance(pressedX, pressedY, clickX, clickY) < MIN_DIS) {
                    mItemClickCallback.onClick(mPosition);
                    return true;
                }
                updateItemBackground(component, touchEvent);
                if (!isSwiping && mItemTouchCallback != null) {
                    mItemTouchCallback.onTouch(mPosition);
                    mDownX = 0;
                    mDownY = 0;
                    isSwiping = false;
                    return true;
                }
                upMove(component, touchEvent);
                mDownX = 0;
                mDownY = 0;
                isSwiping = false;
                return true;
            default:
                return true;
        }
    }

    private boolean downEvent(Component component, TouchEvent touchEvent) {
        isMoveAfterDown = false;
        pressStartTime = System.currentTimeMillis();
        pressedX = touchEvent.getPointerPosition(touchEvent.getIndex()).getX();
        pressedY = touchEvent.getPointerPosition(touchEvent.getIndex()).getY();

        mDownX = (int) getRaw(touchEvent, component)[0];
        mDownY = (int) getRaw(touchEvent, component)[1];
        if (isPaused) {
            return true;
        }
        updateItemBackground(component, touchEvent);
        return false;
    }

    private boolean moveEvent(Component component, TouchEvent touchEvent) {
        updateItemBackground(component, touchEvent);
        float moveX = getRaw(touchEvent, component)[0];
        float moveY = getRaw(touchEvent, component)[1];
        float deltaX = DecimalUtils.sub(moveX, mDownX);
        float deltaY = DecimalUtils.sub(moveY, mDownY);
        if (mIsVertical) {
            if (Math.abs(deltaY) > TOUCH_SLOP && Math.abs(deltaX) < Math.abs(deltaY) / HALF) {
                isSwiping = true;
            }
            if (isSwiping) {
                component.setTranslationY(deltaY / HALF);
                component.setAlpha(Math.max(0f, Math.min(1f,
                        DecimalUtils.sub(1f, ALPHA_RATIO * Math.abs(deltaY) / MVIEWWIDTH))));

                return true;
            }
        } else {
            if (Math.abs(deltaX) > TOUCH_SLOP && Math.abs(deltaY) < Math.abs(deltaX) / HALF) {
                isSwiping = true;
            }
            if (isSwiping) {
                component.setTranslationX(deltaX / HALF);
                component.setAlpha(Math.max(0f, Math.min(1f,
                        DecimalUtils.sub(1f, ALPHA_RATIO * Math.abs(deltaX) / MVIEWWIDTH))));
                return true;
            }
        }
        return false;
    }

    private void upMove(Component component, TouchEvent touchEvent) {
        float upX = getRaw(touchEvent, component)[0];
        float upY = getRaw(touchEvent, component)[1];
        float deltaXx = DecimalUtils.sub(upX, mDownX);
        float deltaYy = DecimalUtils.sub(upY, mDownY);
        Hlog.sop("deltaX:" + deltaXx + "-------deltaY:" + deltaYy);
        boolean isDismiss = false;
        boolean isDismissRight = false;
        if (mIsVertical) {
            if (Math.abs(deltaYy) > MVIEWWIDTH / ALPHA_RATIO && isSwiping) {
                isDismiss = true;
                isDismissRight = deltaYy > 0;
            }
            if (isDismiss) {
                component.setTranslationY(isDismissRight ? MVIEWWIDTH : -MVIEWWIDTH);
                component.setAlpha(0);
                mCallbacks.onDismiss(component);
            } else {
                component.setTranslationY(0);
                component.setAlpha(1);
            }
        } else {
            if (Math.abs(deltaXx) > MVIEWWIDTH / ALPHA_RATIO && isSwiping) {
                isDismiss = true;
                isDismissRight = deltaXx > 0;
            }
            if (isDismiss) {
                component.setTranslationX(isDismissRight ? MVIEWWIDTH : -MVIEWWIDTH);
                component.setAlpha(0);
                mCallbacks.onDismiss(component);
            } else {
                component.setTranslationX(0);
                component.setAlpha(1);
            }
        }
    }

    private float[] getRaw(TouchEvent touchEvent, Component component) {
        float[] touch = new float[COUNT];
        int[] location = component.getLocationOnScreen();
        touch[0] = DecimalUtils.add(touchEvent.getPointerScreenPosition(touchEvent.getIndex()).getX(), location[0]);
        touch[1] = DecimalUtils.add(touchEvent.getPointerScreenPosition(touchEvent.getIndex()).getY(), location[1]);
        return touch;
    }

    private void updateItemBackground(final Component component, TouchEvent touchEvent) {
        if (mBackgroundPressId == null || mBackgroundNormalId == null || component == null) {
            return;
        }
        switch (touchEvent.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                if (!isMoveAfterDown) {
                    component.setBackground(mBackgroundPressId);
                }
                break;
            case TouchEvent.PRIMARY_POINT_UP:
            case TouchEvent.CANCEL:
                component.setBackground(mBackgroundNormalId);
                break;
            default:
                break;
        }
    }

    /**
     * setEnabled
     *
     * @param isEnabled
     */
    public void setEnabled(boolean isEnabled) {
        isPaused = !isEnabled;
    }

    private float distance(float x1, float y1, float x2, float y2) {
        float dx = DecimalUtils.sub(x1, x2);
        float dy = DecimalUtils.sub(y1, y2);
        return (float) Math.sqrt(DecimalUtils.add(dx * dx, dy * dy));
    }

    /**
     * 删除回调
     *
     * @author:hello
     * @since 2021-04-25
     */
    public interface DismissCallbacks {
        /**
         * onDismiss
         *
         * @param view
         */
        void onDismiss(Component view);
    }

    /**
     * 触摸回调
     *
     * @author:hello
     * @since 2021-04-25
     */
    public interface OnItemTouchCallBack {
        /**
         * onTouch
         *
         * @param position
         */
        void onTouch(int position);
    }

    /**
     * 点击回调
     *
     * @author:hello
     * @since 2021-04-25
     */
    public interface OnItemClickCallBack {
        /**
         * onClick
         *
         * @param position
         */
        void onClick(int position);
    }

    /**
     * Builder
     *
     * @author:hello
     * @since 2021-04-25
     */
    public static class Builder {
        private final int mPosition;
        private final Component mChild;
        private final DismissCallbacks mCallbacks;

        private OnItemTouchCallBack mItemTouchCallback = null;
        private OnItemClickCallBack mItemClickCallback = null;
        private boolean mIsVertical = false;
        private ShapeElement mBackgroundPressId;
        private ShapeElement mBackgroundNormalId;

        /**
         * onClick
         *
         * @param pos
         * @param child
         * @param callbacks
         */
        public Builder(int pos, Component child, DismissCallbacks callbacks) {
            mPosition = pos;
            mChild = child;
            mCallbacks = callbacks;
        }

        /**
         * setIsVertical
         *
         * @param isVertical
         * @return this
         */
        public Builder setIsVertical(boolean isVertical) {
            mIsVertical = isVertical;
            return this;
        }

        /**
         * setItemTouchCallback
         *
         * @param callBack
         * @return this
         */
        public Builder setItemTouchCallback(OnItemTouchCallBack callBack) {
            mItemTouchCallback = callBack;
            return this;
        }

        /**
         * setItemClickCallback
         *
         * @param callBack
         * @return this
         */
        public Builder setItemClickCallback(OnItemClickCallBack callBack) {
            mItemClickCallback = callBack;
            return this;
        }

        /**
         * setBackgroundId
         *
         * @param normalId
         * @param pressId
         * @return this
         */
        public Builder setBackgroundId(ShapeElement normalId, ShapeElement pressId) {
            mBackgroundNormalId = normalId;
            mBackgroundPressId = pressId;
            return this;
        }

        /**
         * create
         *
         * @return this
         */
        public SwipeDismissRecyclerViewTouchListener create() {
            return new SwipeDismissRecyclerViewTouchListener(this);
        }
    }
}
